/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Apache License, Version 2.0, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Scripting.Utils;
using System.Reflection.Emit;
using Microsoft.Scripting.Generation;
using System.Reflection;

namespace Microsoft.Scripting.Actions {
    // TODO: replace this class with calls to Expression.GetDelegateType
    public static class DynamicSiteHelpers {

        private delegate object CreateSite(CallSiteBinder binder);

        private const int MaximumArity = 17;

        private static Dictionary<ICollection<Type>, Type> _DelegateTypes;

        // TODO: remove in favor of Expression.GetDelegateType
        public static Type MakeCallSiteDelegate(params Type[] types) {
            Debug.Assert(types != null);
            return MakeDelegate(types.AddFirst(typeof(CallSite)));
        }

        private static Type MakeDelegate(Type[] types) {
            return GetStandardDelegateType(types) ?? MakeCustomDelegate(types);
        }

        public static Type GetStandardDelegateType(Type[] types) {
            ContractUtils.RequiresNotEmpty(types, "types");

            // Can only used predefined delegates if we have no byref types and
            // the arity is small enough to fit in Func<...> or Action<...>
            if (types.Length > MaximumArity || Any(types, t => t.IsByRef)) {
                return null;
            }

            Type result;
            if (types[types.Length - 1] == typeof(void)) {
                result = GetActionType(RemoveLast(types));
            } else {
                result = GetFuncType(types);
            }
            Debug.Assert(result != null);
            return result;
        }

        private static Type GetFuncType(Type[] types) {
            switch (types.Length) {
                #region Generated Delegate Microsoft Scripting Scripting Func Types

                // *** BEGIN GENERATED CODE ***
                // generated by function: gen_delegate_func from: generate_dynsites.py

                case 1: return typeof(Func<>).MakeGenericType(types);
                case 2: return typeof(Func<,>).MakeGenericType(types);
                case 3: return typeof(Func<,,>).MakeGenericType(types);
                case 4: return typeof(Func<,,,>).MakeGenericType(types);
                case 5: return typeof(Func<,,,,>).MakeGenericType(types);
                case 6: return typeof(Func<,,,,,>).MakeGenericType(types);
                case 7: return typeof(Func<,,,,,,>).MakeGenericType(types);
                case 8: return typeof(Func<,,,,,,,>).MakeGenericType(types);
                case 9: return typeof(Func<,,,,,,,,>).MakeGenericType(types);
                case 10: return typeof(Func<,,,,,,,,,>).MakeGenericType(types);
                case 11: return typeof(Func<,,,,,,,,,,>).MakeGenericType(types);
                case 12: return typeof(Func<,,,,,,,,,,,>).MakeGenericType(types);
                case 13: return typeof(Func<,,,,,,,,,,,,>).MakeGenericType(types);
                case 14: return typeof(Func<,,,,,,,,,,,,,>).MakeGenericType(types);
                case 15: return typeof(Func<,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 16: return typeof(Func<,,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 17: return typeof(Func<,,,,,,,,,,,,,,,,>).MakeGenericType(types);

                // *** END GENERATED CODE ***

                #endregion

                default: return null;
            }
        }

        private static Type GetActionType(Type[] types) {
            switch (types.Length) {
                case 0: return typeof(Action);

                #region Generated Delegate Microsoft Scripting Action Types

                // *** BEGIN GENERATED CODE ***
                // generated by function: gen_delegate_action from: generate_dynsites.py

                case 1: return typeof(Action<>).MakeGenericType(types);
                case 2: return typeof(Action<,>).MakeGenericType(types);
                case 3: return typeof(Action<,,>).MakeGenericType(types);
                case 4: return typeof(Action<,,,>).MakeGenericType(types);
                case 5: return typeof(Action<,,,,>).MakeGenericType(types);
                case 6: return typeof(Action<,,,,,>).MakeGenericType(types);
                case 7: return typeof(Action<,,,,,,>).MakeGenericType(types);
                case 8: return typeof(Action<,,,,,,,>).MakeGenericType(types);
                case 9: return typeof(Action<,,,,,,,,>).MakeGenericType(types);
                case 10: return typeof(Action<,,,,,,,,,>).MakeGenericType(types);
                case 11: return typeof(Action<,,,,,,,,,,>).MakeGenericType(types);
                case 12: return typeof(Action<,,,,,,,,,,,>).MakeGenericType(types);
                case 13: return typeof(Action<,,,,,,,,,,,,>).MakeGenericType(types);
                case 14: return typeof(Action<,,,,,,,,,,,,,>).MakeGenericType(types);
                case 15: return typeof(Action<,,,,,,,,,,,,,,>).MakeGenericType(types);
                case 16: return typeof(Action<,,,,,,,,,,,,,,,>).MakeGenericType(types);

                // *** END GENERATED CODE ***

                #endregion
                default: return null;
            }
        }

        private static T[] AddFirst<T>(this IList<T> list, T item) {
            T[] res = new T[list.Count + 1];
            res[0] = item;
            list.CopyTo(res, 1);
            return res;
        }

        private static bool Any<T>(this IEnumerable<T> source, Func<T, bool> predicate) {
            foreach (T element in source) {
                if (predicate(element)) {
                    return true;
                }
            }
            return false;
        }

        private static T[] RemoveLast<T>(this T[] array) {
            T[] result = new T[array.Length - 1];
            Array.Copy(array, 0, result, 0, result.Length);
            return result;
        }

        private static Type MakeCustomDelegate(Type[] types) {
            if (_DelegateTypes == null) {
                Interlocked.CompareExchange(
                    ref _DelegateTypes,
                    new Dictionary<ICollection<Type>, Type>(ListEqualityComparer<Type>.Instance),
                    null
                );
            }

            bool found;
            Type type;

            //
            // LOCK to retrieve the delegate type, if any
            //

            lock (_DelegateTypes) {
                found = _DelegateTypes.TryGetValue(types, out type);
            }

            if (!found && type != null) {
                return type;
            }

            //
            // Create new delegate type
            //

            type = MakeNewCustomDelegate(types);

            //
            // LOCK to insert new delegate into the cache. If we already have one (racing threads), use the one from the cache
            //

            lock (_DelegateTypes) {
                Type conflict;
                if (_DelegateTypes.TryGetValue(types, out conflict) && conflict != null) {
                    type = conflict;
                } else {
                    _DelegateTypes[types] = type;
                }
            }

            return type;
        }

        private static Type MakeNewCustomDelegate(Type[] types) {
            Type returnType = types[types.Length - 1];
            Type[] parameters = types.RemoveLast();

            return Snippets.Shared.DefineDelegate("Delegate" + types.Length, returnType, parameters);
        }

        /// <summary>
        /// Returns true if the method should not be displayed in the stack frame.
        /// </summary>
        public static bool IsInvisibleDlrStackFrame(MethodBase mb) {
            //This method name is used for the dynamic method created for a delegate type signature.
            if (mb.Name == "_Scripting_") {
                return true;
            }

            //Filters out methods in Microsoft.Scripting namespaces.
            if (mb.DeclaringType != null && 
                mb.DeclaringType.Namespace != null &&
                mb.DeclaringType.Namespace.StartsWith("Microsoft.Scripting", StringComparison.Ordinal)) {
                return true;
            }

            //Filters out all the methods generated for DLR rules or used in DLR rules.
            return CallSiteHelpers.IsInternalFrame(mb);
        }
    }
}
